<template>
  <div class="free-play">
    <home-header />
    <div class="free-play-content">
      <!-- 操作区域（文字按钮） -->
      <div class="toolbar">
        <button class="toolbar-text-btn" @click="showKeyboardSettings">
          <i class="el-icon-setting"></i>
          设置键盘映射
        </button>
        <button class="toolbar-text-btn" @click="toggleNoteNames">
          <i :class="showNoteNames ? 'el-icon-hide' : 'el-icon-view'"></i>
          {{ showNoteNames ? '隐藏音名' : '显示音名' }}
        </button>
        <el-popover
          placement="bottom"
          width="200"
          trigger="click"
          v-model="showVolumePopover">
          <div class="volume-control">
            <el-slider
              v-model="volume"
              :min="0"
              :max="100"
              :format-tooltip="formatVolume"
              @change="handleVolumeChange">
            </el-slider>
          </div>
          <button class="toolbar-text-btn" slot="reference">
            <i class="el-icon-headset"></i>
            调整音量
          </button>
        </el-popover>
      </div>
      <div class="waterfall-area">
        <!-- 瀑布流容器 -->
        <div class="waterfall-container">
          <div
            v-for="bar in waterfallBars"
            :key="bar.id"
            class="waterfall-bar"
            :style="{
              left: `calc(${bar.left}%)`,
              width: bar.width + '%',
              animationDuration: bar.duration + 'ms',
              height: '40px',
              background: bar.color.bg,
              boxShadow: `0 0 15px ${bar.color.glow}, 0 0 30px rgba(0, 0, 0, 0.4)`,
              '--duration': bar.duration + 'ms'
            }"
          ></div>
        </div>
      </div>
    </div>
    <!-- 吸底钢琴键盘 -->
    <div class="keyboard-fixed-bottom">
      <div class="keyboard-box">
        <div class="piano-bottom">
          <div class="piano">
            <div v-for="(key, index) in pianoKeys" :key="index" 
                 :id="key"
                 :data-id="key"
                 class="piano-key"
                 :class="{ 'black': key.includes('#') || key.includes('b') }"
                 @click="handlePianoKeyClick(index)">
              <span v-if="showNoteNames && noteNames[index]" :class="noteNames[index].pitch">
                {{ noteNames[index].name }}
              </span>
            </div>
          </div>
          <ul class="piano-group">
            <li v-for="(width, index) in groupWidths" 
                :key="index" 
                class="group"
                :style="{ width: width }">
              {{ index + 1 }}组
            </li>
          </ul>
        </div>
      </div>
    </div>

    <!-- 键盘设置弹窗 -->
    <div class="keyboard-settings-modal" v-if="showKeyboardModal">
      <div class="modal-content">
        <div class="modal-header">
          <h2>设置键盘映射</h2>
          <span class="close" @click="showKeyboardModal = false">&times;</span>
        </div>
        <div class="modal-body">
          <div class="keyboard" id="keyboard">
            <div class="function-keys">
              <div v-for="(key, index) in functionKeys" :key="key.code + '-' + index" class="key" :class="{mapped: isMappedKey(key.code)}" :data-code="key.code" @click="startEditKeyMapping(key.code, $event)">
                <span>{{ key.key }}</span>
                <span v-if="keyToPianoMap[key.code] !== undefined" style="font-size:10px;color:#888;">→{{ getNoteLabelText(keyToPianoMap[key.code]) }}</span>
              </div>
            </div>
            <div class="main-keyboard">
              <div v-for="(key, index) in mainKeyboard" :key="key.code + '-' + index" class="key" :class="{'colspan2': key.colspan2, 'colspan9': key.colspan9, mapped: isMappedKey(key.code)}" :data-code="key.code" @click="startEditKeyMapping(key.code, $event)">
                <span>{{ key.key.replace(/[<>]/g, '') }}</span>
                <span v-if="keyToPianoMap[key.code] !== undefined" style="font-size:10px;color:#888;">→{{ getNoteLabelText(keyToPianoMap[key.code]) }}</span>
              </div>
            </div>
            <div class="numpad">
              <div v-for="(key, index) in numpad" :key="key.code + '-' + index" class="key" :class="{'rowspan2': key.rowspan2, mapped: isMappedKey(key.code)}" :data-code="key.code" @click="startEditKeyMapping(key.code, $event)">
                <span>{{ key.key }}</span>
                <span v-if="keyToPianoMap[key.code] !== undefined" style="font-size:10px;color:#888;">→{{ getNoteLabelText(keyToPianoMap[key.code]) }}</span>
              </div>
            </div>
            <div v-if="mappingPopover.show" class="mapping-popover" :style="{left: mappingPopover.left + 'px', top: mappingPopover.top + 'px'}">
              <span class="close-btn" @click="closeMappingPopover">×</span>
              <el-select
                v-model="keyToPianoMap[mappingPopover.keyCode]"
                placeholder="选择钢琴键"
                @change="val => updateKeyMapping(mappingPopover.keyCode, val)"
                filterable>
                <el-option
                  v-for="idx in getAvailablePianoIndexes(mappingPopover.keyCode)"
                  :key="idx"
                  :label="getNoteLabelText(idx)"
                  :value="idx"
                >
                  <span v-html="getNoteLabel(idx)"></span>
                </el-option>
              </el-select>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import HomeHeader from '@/components/HomeHeader.vue'

let barId = 0;

export default {
  name: 'FreePlayView',
  components: {
    HomeHeader
  },
  data() {
    return {
      showNoteNames: true,
      pianoKeys: [
        'A0', 'A#0/Bb0', 'B0',
        'C1', 'C#1/Db1', 'D1', 'D#1/Eb1', 'E1', 'F1', 'F#1/Gb1', 'G1', 'G#1/Ab1',
        'A1', 'A#1/Bb1', 'B1',
        'C2', 'C#2/Db2', 'D2', 'D#2/Eb2', 'E2', 'F2', 'F#2/Gb2', 'G2', 'G#2/Ab2',
        'A2', 'A#2/Bb2', 'B2',
        'C3', 'C#3/Db3', 'D3', 'D#3/Eb3', 'E3', 'F3', 'F#3/Gb3', 'G3', 'G#3/Ab3',
        'A3', 'A#3/Bb3', 'B3',
        'C4', 'C#4/Db4', 'D4', 'D#4/Eb4', 'E4', 'F4', 'F#4/Gb4', 'G4', 'G#4/Ab4',
        'A4', 'A#4/Bb4', 'B4',
        'C5', 'C#5/Db5', 'D5', 'D#5/Eb5', 'E5', 'F5', 'F#5/Gb5', 'G5', 'G#5/Ab5',
        'A5', 'A#5/Bb5', 'B5',
        'C6', 'C#6/Db6', 'D6', 'D#6/Eb6', 'E6', 'F6', 'F#6/Gb6', 'G6', 'G#6/Ab6',
        'A6', 'A#6/Bb6', 'B6',
        'C7', 'C#7/Db7', 'D7', 'D#7/Eb7', 'E7', 'F7', 'F#7/Gb7', 'G7', 'G#7/Ab7',
        'A7', 'A#7/Bb7', 'B7',
        'C8'
      ],
      noteNames: [
        { name: '6', pitch: 'QLP' },
        { name: '升6降7', pitch: 'QLP' },
        { name: '7', pitch: 'QLP' },
        { name: '1', pitch: 'TLP' },
        { name: '升1降2', pitch: 'TLP' },
        { name: '2', pitch: 'TLP' },
        { name: '升2降3', pitch: 'TLP' },
        { name: '3', pitch: 'TLP' },
        { name: '4', pitch: 'TLP' },
        { name: '升4降5', pitch: 'TLP' },
        { name: '5', pitch: 'TLP' },
        { name: '升5降6', pitch: 'TLP' },
        { name: '6', pitch: 'TLP' },
        { name: '升6降7', pitch: 'TLP' },
        { name: '7', pitch: 'TLP' },
        { name: '1', pitch: 'DLP' },
        { name: '升1降2', pitch: 'DLP' },
        { name: '2', pitch: 'DLP' },
        { name: '升2降3', pitch: 'DLP' },
        { name: '3', pitch: 'DLP' },
        { name: '4', pitch: 'DLP' },
        { name: '升4降5', pitch: 'DLP' },
        { name: '5', pitch: 'DLP' },
        { name: '升5降6', pitch: 'DLP' },
        { name: '6', pitch: 'DLP' },
        { name: '升6降7', pitch: 'DLP' },
        { name: '7', pitch: 'DLP' },
        { name: '1', pitch: 'LP' },
        { name: '升1降2', pitch: 'LP' },
        { name: '2', pitch: 'LP' },
        { name: '升2降3', pitch: 'LP' },
        { name: '3', pitch: 'LP' },
        { name: '4', pitch: 'LP' },
        { name: '升4降5', pitch: 'LP' },
        { name: '5', pitch: 'LP' },
        { name: '升5降6', pitch: 'LP' },
        { name: '6', pitch: 'LP' },
        { name: '升6降7', pitch: 'LP' },
        { name: '7', pitch: 'LP' },
        { name: '1', pitch: 'MP' },
        { name: '升1降2', pitch: 'MP' },
        { name: '2', pitch: 'MP' },
        { name: '升2降3', pitch: 'MP' },
        { name: '3', pitch: 'MP' },
        { name: '4', pitch: 'MP' },
        { name: '升4降5', pitch: 'MP' },
        { name: '5', pitch: 'MP' },
        { name: '升5降6', pitch: 'MP' },
        { name: '6', pitch: 'MP' },
        { name: '升6降7', pitch: 'MP' },
        { name: '7', pitch: 'MP' },
        { name: '1', pitch: 'HP' },
        { name: '升1降2', pitch: 'HP' },
        { name: '2', pitch: 'HP' },
        { name: '升2降3', pitch: 'HP' },
        { name: '3', pitch: 'HP' },
        { name: '4', pitch: 'HP' },
        { name: '升4降5', pitch: 'HP' },
        { name: '5', pitch: 'HP' },
        { name: '升5降6', pitch: 'HP' },
        { name: '6', pitch: 'HP' },
        { name: '升6降7', pitch: 'HP' },
        { name: '7', pitch: 'HP' },
        { name: '1', pitch: 'DHP' },
        { name: '升1降2', pitch: 'DHP' },
        { name: '2', pitch: 'DHP' },
        { name: '升2降3', pitch: 'DHP' },
        { name: '3', pitch: 'DHP' },
        { name: '4', pitch: 'DHP' },
        { name: '升4降5', pitch: 'DHP' },
        { name: '5', pitch: 'DHP' },
        { name: '升5降6', pitch: 'DHP' },
        { name: '6', pitch: 'DHP' },
        { name: '升6降7', pitch: 'DHP' },
        { name: '7', pitch: 'DHP' },
        { name: '1', pitch: 'THP' },
        { name: '升1降2', pitch: 'THP' },
        { name: '2', pitch: 'THP' },
        { name: '升2降3', pitch: 'THP' },
        { name: '3', pitch: 'THP' },
        { name: '4', pitch: 'THP' },
        { name: '升4降5', pitch: 'THP' },
        { name: '5', pitch: 'THP' },
        { name: '升5降6', pitch: 'THP' },
        { name: '6', pitch: 'THP' },
        { name: '升6降7', pitch: 'THP' },
        { name: '7', pitch: 'THP' },
        { name: '1', pitch: 'QHP' }
      ],
      keysInGroup: [3, 12, 12, 12, 12, 12, 12, 12, 1],
      groupWidths: ['3.75%', '13.35%', '13.35%', '13.35%', '13.35%', '13.35%', '13.35%', '13.35%', '1.95%'],
      audioContext: null,
      audioMap: {},
      waterfallBars: [],
      barColors: [
        { bg: '#7ed6df', glow: 'rgba(126, 214, 223, 0.8)' },
        { bg: '#22a6b3', glow: 'rgba(34, 166, 179, 0.8)' },
        { bg: '#6ab04c', glow: 'rgba(106, 176, 76, 0.8)' },
        { bg: '#eb4d4b', glow: 'rgba(235, 77, 75, 0.8)' },
        { bg: '#f0932b', glow: 'rgba(240, 147, 43, 0.8)' },
        { bg: '#be2edd', glow: 'rgba(190, 46, 221, 0.8)' },
        { bg: '#4834d4', glow: 'rgba(72, 52, 212, 0.8)' },
        { bg: '#6c5ce7', glow: 'rgba(108, 92, 231, 0.8)' },
        { bg: '#00b894', glow: 'rgba(0, 184, 148, 0.8)' },
        { bg: '#0984e3', glow: 'rgba(9, 132, 227, 0.8)' }
      ],
      showKeyboardModal: false,
      selectedTuning: 'C 调',
      selectedChord: '',
      showKeyboardMap: true,
      showPcKeyboard: true,
      functionKeys: [
        { key: "Esc", code: "27" },
        { key: "F1", code: "112" },
        { key: "F2", code: "113" },
        { key: "F3", code: "114" },
        { key: "F4", code: "115" },
        { key: "F5", code: "116" },
        { key: "F6", code: "117" },
        { key: "F7", code: "118" },
        { key: "F8", code: "119" },
        { key: "F9", code: "120" },
        { key: "F10", code: "121" },
        { key: "F11", code: "122" },
        { key: "F12", code: "123" }
      ],
      mainKeyboard: [
        { key: "`", code: "192" }, { key: "1", code: "49" }, { key: "2", code: "50" }, { key: "3", code: "51" }, { key: "4", code: "52" }, { key: "5", code: "53" }, { key: "6", code: "54" }, { key: "7", code: "55" }, { key: "8", code: "56" }, { key: "9", code: "57" }, { key: "0", code: "48" }, { key: "-", code: "189" }, { key: "+", code: "187" }, { key: "back", code: "8" },
        { key: "Tab", code: "9" }, { key: "Q", code: "81", pianoKey: '51' }, { key: "W", code: "87", pianoKey: '53' }, { key: "E", code: "69", pianoKey: '55' }, { key: "R", code: "82", pianoKey: '56' }, { key: "T", code: "84", pianoKey: '58' }, { key: "Y", code: "89", pianoKey: '60' }, { key: "U", code: "85", pianoKey: '62' }, { key: "I", code: "73" }, { key: "O", code: "79" }, { key: "P", code: "80" }, { key: "{", code: "219" }, { key: "}", code: "221" }, { key: "\\", code: "220" },
        { key: "Lock", code: "20" }, { key: "A", code: "65", pianoKey: '39' }, { key: "S", code: "83", pianoKey: '41' }, { key: "D", code: "68", pianoKey: '43' }, { key: "F", code: "70", pianoKey: '44' }, { key: "G", code: "71", pianoKey: '46' }, { key: "H", code: "72", pianoKey: '48' }, { key: "J", code: "74", pianoKey: '50' }, { key: "K", code: "75" }, { key: "L", code: "76" }, { key: ";", code: "186" }, { key: "\"", code: "222" }, { key: "Enter", code: "13", colspan2: true },
        { key: "Shift", code: "16", colspan2: true }, { key: "Z", code: "90", pianoKey: '27' }, { key: "X", code: "88", pianoKey: '29' }, { key: "C", code: "67", pianoKey: '31' }, { key: "V", code: "86", pianoKey: '32' }, { key: "B", code: "66", pianoKey: '34' }, { key: "N", code: "78", pianoKey: '36' }, { key: "M", code: "77", pianoKey: '38' }, { key: "<", code: "188" }, { key: ">", code: "190" }, { key: "?", code: "191" }, { key: "Shift", code: "16", colspan2: true },
        { key: "Ctrl", code: "17" }, { key: "win", code: "91" }, { key: "alt", code: "18" }, { key: "blank", code: "32", colspan9: true }, { key: "alt", code: "18" }, { key: "Ctrl", code: "17" },
      ],
      numpad: [
        { key: "Num", code: "144" }, { key: "/", code: "111" }, { key: "*", code: "106" }, { key: "-", code: "109" },
        { key: "7", code: "103" }, { key: "8", code: "104" }, { key: "9", code: "105" }, { key: "+", code: "107", rowspan2: true },
        { key: "4", code: "100" }, { key: "5", code: "101" }, { key: "6", code: "102" },
        { key: "1", code: "97" }, { key: "2", code: "98" }, { key: "3", code: "99" }, { key: "Enter", code: "13", rowspan2: true }, { key: "0", code: "96" },
        { key: ".", code: "110" },
      ],
      scales: {
        'C 调': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
        '#C 调': ['C#4/Db4', 'D#4/Eb4', 'F4', 'F#4/Gb4', 'G#4/Ab4', 'A#4/Bb4', 'C5','C#5/Db5'],
        'D 调': ['D4', 'E4', 'F#4/Gb4', 'G4', 'A4', 'B4', 'C#5/Db5','D5'],
        '#D 调': ['D#4/Eb4', 'F4', 'G4', 'G#4/Ab4', 'A#4/Bb4', 'C5', 'D5','D#5/Eb5'],
        'E 调': ['E4', 'F#4/Gb4', 'G#4/Ab4', 'A4', 'B4', 'C#5/Db5', 'D#5/Eb5','E5'],
        'F 调': ['F4', 'G4', 'A4', 'A#4/Bb4', 'C5', 'D5', 'E5','F5'],
        '#F 调': ['F#4/Gb4', 'G#4/Ab4', 'A#4/Bb4', 'B4', 'C#5/Db5', 'D#5/Eb5','F5','F#5/Gb5'],
        'G 调': ['G4', 'A4', 'B4', 'C5', 'D5', 'E5', 'F#5/Gb5','G5'],
        '#G 调': ['G#4/Ab4', 'A#4/Bb4', 'C5', 'C#5/Db5', 'D#5/Eb5', 'F5', 'G5','G#5/Ab5'],
        'A 调': ['A4', 'B4', 'C#5/Db5', 'D5', 'E5', 'F#5/Gb5', 'G#5/Ab5','A5'],
        '#A 调': ['A#4/Bb4', 'C5', 'D5', 'D#5/Eb5', 'F5', 'G5', 'A5','A#5/Bb5'],
        'B 调': ['B4', 'C#5/Db5', 'D#5/Eb5', 'E5', 'F#5/Gb5', 'G#5/Ab5', 'A#5/Bb5','B5'],
      },
      chords: {
        'C': ['C4', 'E4', 'G4'],
        'Cm': ['C4', 'D#4/Eb4', 'G4'],
        'D': ['D4', 'F#4/Gb4', 'A4'],
        'Dm': ['D4', 'F4', 'A4'],
        'E': ['E4', 'G#4/Ab4', 'B4'],
        'Em': ['E4', 'G4', 'B4'],
        'F': ['F4', 'A4', 'C5'],
        'Fm': ['F4', 'G#4/Ab4', 'C5'],
        'G': ['G4', 'B4', 'D5'],
        'Gm': ['G4', 'A#4/Bb4', 'D5'],
        'A': ['A4', 'C#5/Db5', 'E5'],
        'Am': ['A4', 'C5', 'E5'],
      },
      keyToPianoMap: {
        '65': 39, // A -> C4
        '83': 41, // S -> D4
        '68': 43, // D -> E4
        '70': 44, // F -> F4
        '71': 46, // G -> G4
        '72': 48, // H -> A4
        '74': 50, // J -> B4
        '87': 40, // W -> C#4/Db4
        '69': 42, // E -> D#4/Eb4
        '84': 45, // T -> F#4/Gb4
        '89': 47, // Y -> G#4/Ab4
        '85': 49, // U -> A#4/Bb4
      },
      activeKeys: new Set(),
      editingKey: null,
      mappingPopover: {
        show: false,
        keyCode: null,
        left: 0,
        top: 0
      },
      volume: 100,
      showVolumePopover: false,
    }
  },
  computed: {
  },
  mounted() {
    this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
    // 补全noteNames为88项
    if (this.noteNames.length < this.pianoKeys.length) {
      for (let i = this.noteNames.length; i < this.pianoKeys.length; i++) {
        this.noteNames.push({ name: '', pitch: '' });
      }
    }
    // 本地存储映射
    const savedMap = localStorage.getItem('keyToPianoMap');
    if (savedMap) {
      try {
        this.keyToPianoMap = JSON.parse(savedMap);
      } catch (e) {}
    }
    window.addEventListener('keydown', this.handleKeyDown);
    window.addEventListener('keyup', this.handleKeyUp);
  },
  beforeDestroy() {
    // 清理音频资源
    Object.values(this.audioMap).forEach(audio => {
      audio.pause();
      audio.currentTime = 0;
    });
    this.audioMap = {};
    window.removeEventListener('keydown', this.handleKeyDown);
    window.removeEventListener('keyup', this.handleKeyUp);
  },
  methods: {
    toggleNoteNames() {
      this.showNoteNames = !this.showNoteNames;
    },
    handlePianoKeyClick(index) {
      const id = this.pianoKeys[index];
      const audio = new Audio();
      let noteName;
      if (id.includes('/')) {
        noteName = id.split('/')[1];
      } else {
        noteName = id;
      }
      audio.src = `/audio/salamander/${noteName}.mp3`;
      audio.volume = this.volume / 100;
      this.audioMap[noteName] = audio;
      audio.onerror = (e) => {
        console.error('Error loading audio:', e);
        this.$message && this.$message.error && this.$message.error('音频加载失败');
      };
      audio.currentTime = 0;
      audio.play().catch(error => {
        console.error('Error playing audio:', error);
        this.$message && this.$message.error && this.$message.error('音频播放失败');
      });
      // 保留瀑布流逻辑
      const totalKeys = this.pianoKeys.length;
      const barWidth = 100 / totalKeys;
      const barLeft = index * barWidth;
      const duration = 2000;
      const randomColor = this.barColors[Math.floor(Math.random() * this.barColors.length)];
      const newBar = {
        id: barId++,
        left: barLeft,
        width: barWidth,
        duration,
        color: randomColor,
        style: {
          '--duration': duration + 'ms'
        }
      };
      this.waterfallBars.push(newBar);
      setTimeout(() => {
        this.waterfallBars = this.waterfallBars.filter(bar => bar.id !== newBar.id);
      }, duration);
    },
    showKeyboardSettings() {
      this.showKeyboardModal = true;
    },
    handleTuningChange() {
      this.highlightPianoKeysForScale(this.selectedTuning);
    },
    handleChordChange() {
      if (this.selectedChord) {
        const selectedIndex = Object.keys(this.scales).indexOf(this.selectedTuning);
        this.highlightPianoKeys(this.selectedChord, selectedIndex);
      }
    },
    highlightPianoKeysForScale(scaleName) {
      const keysToHighlight = this.scales[scaleName];
      keysToHighlight.forEach((key) => {
        const index = this.pianoKeys.indexOf(key);
        if (index !== -1) {
          if (key.includes('#') || key.includes('b')) {
            this.$el.querySelector(`.piano-key:nth-child(${index + 1})`).classList.add('highlighted');
          } else {
            this.$el.querySelector(`#${key}`).classList.add('highlighted');
          }
        }
      });
    },
    highlightPianoKeys(chordName, mode) {
      let keysToHighlight = this.chords[chordName];
      if (mode > 0) {
        let transposedChord = [];
        for (let note of keysToHighlight) {
          const index = this.pianoKeys.indexOf(note);
          if (index !== -1) {
            let transposedIndex = index + mode;
            if (transposedIndex < 0) {
              transposedIndex += this.pianoKeys.length;
            } else if (transposedIndex >= this.pianoKeys.length) {
              transposedIndex -= this.pianoKeys.length;
            }
            transposedChord.push(this.pianoKeys[transposedIndex]);
          } else {
            transposedChord.push(null);
          }
        }
        keysToHighlight = transposedChord;
      }
      keysToHighlight.forEach((key) => {
        if (key) {
          this.$el.querySelector(`.piano-key[data-id="${key}"]`).classList.add('highlighted');
        }
      });
    },
    handleKeyDown(e) {
      const code = String(e.keyCode);
      if (this.keyToPianoMap[code] !== undefined) {
        if (!this.activeKeys.has(code)) {
          this.activeKeys.add(code);
          // 高亮钢琴键
          const idx = this.keyToPianoMap[code];
          const pianoEls = this.$el.querySelectorAll('.keyboard-fixed-bottom .piano-key');
          if (pianoEls[idx]) {
            pianoEls[idx].classList.add('active');
          }
          // 播放音频
          this.handlePianoKeyClick(idx);
        }
      }
    },
    handleKeyUp(e) {
      const code = String(e.keyCode);
      if (this.keyToPianoMap[code] !== undefined) {
        if (this.activeKeys.has(code)) {
          this.activeKeys.delete(code);
          // 取消高亮钢琴键
          const idx = this.keyToPianoMap[code];
          const pianoEls = this.$el.querySelectorAll('.keyboard-fixed-bottom .piano-key');
          if (pianoEls[idx]) {
            pianoEls[idx].classList.remove('active');
          }
        }
      }
    },
    startEditKeyMapping(keyCode, $event) {
      // 获取按钮位置用于定位弹窗，考虑页面滚动
      const rect = $event.currentTarget.getBoundingClientRect();
      this.mappingPopover = {
        show: true,
        keyCode,
        left: rect.left + rect.width / 2 + window.scrollX,
        top: rect.top + rect.height + 8 + window.scrollY
      };
    },
    updateKeyMapping(keyCode, pianoIdx) {
      this.$set(this.keyToPianoMap, keyCode, Number(pianoIdx));
      this.mappingPopover.show = false;
      // 保存到本地
      localStorage.setItem('keyToPianoMap', JSON.stringify(this.keyToPianoMap));
    },
    closeMappingPopover() {
      this.mappingPopover.show = false;
    },
    isMappedKey(keyCode) {
      return this.keyToPianoMap[keyCode] !== undefined;
    },
    getNoteLabel(idx) {
      let label = (this.noteNames[idx] && this.noteNames[idx].name) ? this.noteNames[idx].name : this.pianoKeys[idx];
      const pitch = this.noteNames[idx] ? this.noteNames[idx].pitch : '';
      let dots = '';
      let html = '';
      if (pitch === 'HP') dots = '·';
      if (pitch === 'DHP') dots = '··';
      if (pitch === 'THP') dots = '···';
      if (pitch === 'QHP') dots = '····';
      if (pitch === 'LP') dots = '.';
      if (pitch === 'DLP') dots = '..';
      if (pitch === 'TLP') dots = '...';
      if (pitch === 'QLP') dots = '....';
      if (pitch === 'HP' || pitch === 'DHP' || pitch === 'THP' || pitch === 'QHP') {
        html = `<span class=\"upper-dot\">${dots}</span>${label}`;
      } else if (pitch === 'LP' || pitch === 'DLP' || pitch === 'TLP' || pitch === 'QLP') {
        html = `${label}<span class=\"lower-dot\">${dots}</span>`;
      } else {
        html = label;
      }
      return html;
    },
    getNoteLabelText(idx) {
      let label = (this.noteNames[idx] && this.noteNames[idx].name) ? this.noteNames[idx].name : this.pianoKeys[idx];
      const pitch = this.noteNames[idx] ? this.noteNames[idx].pitch : '';
      let dots = '';
      if (pitch === 'HP') dots = '·';
      if (pitch === 'DHP') dots = '··';
      if (pitch === 'THP') dots = '···';
      if (pitch === 'QHP') dots = '····';
      if (pitch === 'LP') dots = '.';
      if (pitch === 'DLP') dots = '..';
      if (pitch === 'TLP') dots = '...';
      if (pitch === 'QLP') dots = '....';
      if (pitch === 'HP' || pitch === 'DHP' || pitch === 'THP' || pitch === 'QHP') {
        return dots + label;
      } else if (pitch === 'LP' || pitch === 'DLP' || pitch === 'TLP' || pitch === 'QLP') {
        return label + dots;
      } else {
        return label;
      }
    },
    getAvailablePianoIndexes(currentKeyCode) {
      const usedIndexes = Object.entries(this.keyToPianoMap)
        .filter(([k, v]) => k !== String(currentKeyCode))
        .map(([k, v]) => v);
      const allIndexes = Array.from({length: this.pianoKeys.length}, (_, i) => i);
      return allIndexes.filter(idx => !usedIndexes.includes(idx));
    },
    formatVolume(val) {
      return val + '%';
    },
    handleVolumeChange(val) {
      // 更新所有音频元素的音量
      Object.values(this.audioMap).forEach(audio => {
        audio.volume = val / 100;
      });
    },
  }
}
</script>

<style scoped>
.free-play {
  min-height: 100vh;
  background-color: #f5f7fa;
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.free-play-content {
  flex: 1 1 auto;
  padding: 84px 0 0 0;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
}

h2 {
  font-size: 24px;
  color: #333;
  margin-bottom: 24px;
  display: none;
}

.waterfall-area {
  position: relative;
  width: 100%;
  height: calc(100vh - 311px);
  background: #222;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  margin-bottom: 0;
  z-index: 1;
}

/* 瀑布流容器和柱形 */
.waterfall-container {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  pointer-events: none;
  z-index: 10;
  height: 100%;
}
.waterfall-bar {
  position: absolute;
  top: 0;
  height: 40px;
  border-radius: 4px;
  opacity: 0.85;
  animation: waterfall-fall linear forwards;
  overflow: hidden;
}

.waterfall-bar::before {
  content: '';
  position: absolute;
  left: 12%;
  top: 0;
  width: 18%;
  height: 100%;
  border-radius: 8px;
  background: linear-gradient(90deg, rgba(255,255,255,0.7) 0%, rgba(255,255,255,0.15) 100%);
  pointer-events: none;
  z-index: 1;
}

.waterfall-bar::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: inherit;
  border-radius: inherit;
  opacity: 0;
  transform: scale(1);
  animation: explosion 0.3s ease-out forwards;
  animation-delay: calc(var(--duration) - 0.3s);
}

@keyframes waterfall-fall {
  0% {
    top: 0;
    opacity: 1;
  }
  90% {
    opacity: 0.85;
  }
  100% {
    top: calc(100% - 40px);
    opacity: 0;
  }
}

@keyframes explosion {
  0% {
    opacity: 0.8;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(2);
  }
}

/* 吸底钢琴键盘 */
.keyboard-fixed-bottom {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  background: #fff;
  z-index: 100;
  box-shadow: 0 -2px 12px rgba(0,0,0,0.08);
  border-radius: 0 0 12px 12px;
}

.keyboard-box {
  position: relative;
  width: 100%;
  overflow: hidden;
}

.piano-bottom {
  height: 175px;
  background: #000;
  width: 100%;
}

.piano {
  display: flex;
  justify-content: space-between;
  background: #fff;
  width: 100%;
  border: 1px solid #000;
  border-top-width: 3px;
  box-sizing: border-box;
  height: 156px;
}

.piano-key {
  width: 2%;
  height: 152px;
  outline: 1px solid #444444;
  background-color: white;
  box-sizing: border-box;
  line-height: 240px;
  text-align: center;
  position: relative;
  border-radius: 0 0 2px 2px;
  border-bottom: 5px solid #eeeeee;
  cursor: pointer;
  -webkit-user-select: none;
}

.piano-key.black {
  border: none;
  color: #ccc;
  font-size: 12px !important;
  height: 90px;
  display: flex;
  align-items: flex-end;
  padding-bottom: 5px;
  width: 0.9%;
  height: 100px;
  margin: 0 -10px;
  background-color: #444444;
  z-index: 2;
  line-height: normal;
  color: #fff;
  writing-mode: inherit;
}

.piano-key:active {
  background: #000;
  color: #fff !important;
  border-bottom: 5px solid #000;
}

.piano-key.black:active {
  border-bottom: none;
}

.piano-key.active,
.piano-key.active span:after {
  background: red;
  color: #fff !important;
  border-bottom: 5px solid red;
}

.piano-key:active span::after {
  color: #fff !important;
}

.piano-key span::after {
  position: absolute;
  bottom: 36px;
  left: 50%;
  margin-left: -5px;
  display: inline-block;
  width: 10px;
  font-size: 12px;
  line-height: 0;
  text-align: center;
  color: #000;
  writing-mode: tb;
  box-sizing: border-box;
}

.piano-key .LP::after {
  height: 4px;
  content: '.';
}

.piano-key .DLP::after {
  height: 6px;
  content: '..';
}

.piano-key .TLP::after {
  height: 8px;
  content: '...';
}

.piano-key .QLP::after {
  height: 10px;
  content: '....';
}

.piano-key .HP::after {
  height: 4px;
  bottom: 13px;
  content: '.';
}

.piano-key .DHP::after {
  bottom: 10px;
  height: 6px;
  content: '..';
}

.piano-key .THP::after {
  bottom: 8px;
  height: 8px;
  content: '...';
}

.piano-key .QHP::after {
  bottom: 8px;
  height: 8px;
  content: '....';
}

.piano-group {
  overflow: hidden;
  border: 1px solid #000;
  border-width: 0 0.1% 0 0.1%;
}

.piano-group li {
  float: left;
  width: auto;
  background: #ccc;
  font-size: 12px;
  color: #333;
}

.group {
  list-style: none;
  text-align: center;
  margin-top: 3px;
  margin-right: 0.1%;
  box-sizing: border-box;
  white-space: nowrap;
}

.group:last-child {
  margin-right: 0;
}

.toolbar {
  width: 100%;
  display: flex;
  align-items: center;
  padding: 12px 24px 8px 24px;
  background: #fff;
  /* border-radius: 12px; */
  box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  margin-bottom: 0;
  justify-content: flex-start;
  gap: 12px;
}
.toolbar-text-btn {
  background: none;
  border: none;
  color: #1677ff;
  font-size: 16px;
  font-weight: 500;
  padding: 4px 12px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  gap: 6px;
}
.toolbar-text-btn i {
  font-size: 18px;
  transition: transform 0.2s;
}
.toolbar-text-btn:hover {
  background: #e6f4ff;
  color: #0958d9;
}
.toolbar-text-btn:hover i {
  transform: scale(1.1);
}

/* 弹窗样式 */
.keyboard-settings-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}
.modal-content {
  background: #fff;
  width: 90%;
  max-width: 1200px;
  max-height: 90vh;
  border-radius: 8px;
  overflow: hidden;
}
.modal-header {
  padding: 16px 24px;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.modal-header h2 {
  margin: 0;
  font-size: 20px;
  color: #333;
}
.close {
  font-size: 24px;
  color: #666;
  cursor: pointer;
}
.modal-body {
  padding: 24px;
  max-height: calc(90vh - 70px);
  overflow-y: auto;
}

/* 下面是 keyboard 相关样式 */
.piano-main {
  height: calc(100vh - 200px);
  background: #1d1d1d;
  background-image: linear-gradient(to top, #adcefc, #96c4fd);
}
.piano-left {
  width: 80%;
  float: left;
  height: calc(100vh - 200px);
}
.tool-box {
  border-top: 1px solid #666;
  background: #444;
  display: flex;
  height: 40px;
  line-height: 40px;
  padding-left: 30px;
}
.tool-box div {
  margin-right: 20px;
  color: #ccc;
  font-size: 12px;
}
#keyboard {
  display: grid;
  grid-template-areas:
    "function-keys function-keys"
    "main-keyboard numpad";
  gap: 9px;
  border-radius: 5px;
  background-image: linear-gradient(to top, #f9f9f9, #fff);
  padding: 10px;
  box-sizing: border-box;
  margin: 10px;
  font-size: 12px;
  color: #666;
  user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -webkit-user-select: none;
  margin-top: 30px;
  box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.4);
}
.function-keys {
  grid-area: function-keys;
  display: flex;
  gap: 15px;
  border-radius: 5px;
  padding: 5px;
}
.main-keyboard {
  grid-area: main-keyboard;
  display: grid;
  grid-template-columns: repeat(14, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: 5px;
  border-radius: 5px;
  padding: 5px;
}
.numpad {
  grid-area: numpad;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: 5px;
  border-radius: 5px;
  padding: 5px;
}
.key {
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 5px;
  border: 1px solid #ccc;
  background-color: #f0f0f0;
  cursor: pointer;
  transition: background-color 0.3s ease;
  padding: 9px;
  font-size: 14px;
  min-width: 30px;
  box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.key:hover {
  background-color: #999;
  color: #fff;
}
.key.active {
  background: red !important;
  color: #fff !important;
  box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
  transform: translate(1px, 1px);
}
.key.colspan2 {
  grid-column: span 2;
  min-width: 60px;
}
.key.colspan9 {
  grid-column: span 9;
  min-width: 60px;
}
.key.rowspan2 {
  grid-row: span 2;
}
.key.mapped {
  border: 2px solid #409EFF !important;
  background: #e6f7ff !important;
}
.mapping-popover {
  position: fixed;
  z-index: 2000;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.15);
  padding: 16px 20px 12px 20px;
  min-width: 180px;
  border: 1px solid #eee;
}
.mapping-popover .el-select {
  width: 100%;
}
.mapping-popover .close-btn {
  position: absolute;
  top: 6px;
  right: 10px;
  font-size: 18px;
  color: #888;
  cursor: pointer;
}
.upper-dot {
  display: inline-block;
  vertical-align: middle;
  font-size: 12px;
  margin-right: 2px;
}
.lower-dot {
  display: inline-block;
  vertical-align: sub;
  font-size: 12px;
  margin-left: 1px;
}
.volume-control {
  padding: 10px;
}
.volume-control .el-slider {
  margin: 10px 0;
}
.volume-control .el-slider__runway {
  margin: 16px 0;
}
.volume-control .el-slider__bar {
  background-color: #1677ff;
}
.volume-control .el-slider__button {
  border-color: #1677ff;
}
</style> 